{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# ipyleaflet: Interactive maps\n",
    "\n",
    "## A Jupyter - LeafletJS bridge\n",
    "\n",
    "## https://github.com/jupyter-widgets/ipyleaflet\n",
    "\n",
    "\n",
    "ipyleaflet is a jupyter interactive widget library which provides interactive maps to the Jupyter notebook.\n",
    "\n",
    "- MIT Licensed\n",
    "\n",
    "**Installation:**\n",
    "\n",
    "```bash\n",
    "conda install -c conda-forge ipyleaflet\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from __future__ import print_function\n",
    "\n",
    "from ipyleaflet import (\n",
    "    Map,\n",
    "    Marker, MarkerCluster, TileLayer, ImageOverlay, GeoJSON,\n",
    "    Polyline, Polygon, Rectangle, Circle, CircleMarker, Popup,\n",
    "    SplitMapControl, WidgetControl,\n",
    "    basemaps, basemap_to_tiles\n",
    ")\n",
    "\n",
    "from ipywidgets import HTML"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "center = [34.6252978589571, -77.34580993652344]\n",
    "zoom = 10"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Map Widget"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m = Map(center=center, zoom=zoom)\n",
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m.layout.height = '700px'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Every Layer in ipyleaflet is a Widget: TileLayers, Markers, Heatmaps, Videos..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can dynamically change the layers you apply on top of the Map, for example you can dynamically change the base TileLayer and control it through a SelectionSlider widget:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from datetime import date, timedelta\n",
    "from ipywidgets import SelectionSlider\n",
    "\n",
    "n_days = 30\n",
    "days = []\n",
    "\n",
    "# Previous 30 days, starting from today\n",
    "for n in range(n_days):\n",
    "    delta = (n_days - n) * timedelta(days=1)\n",
    "    days.append((date.today() - delta).isoformat())\n",
    "\n",
    "days_slider = SelectionSlider(options=days, value=days[-1], description='Day: ')\n",
    "\n",
    "base_layer = basemap_to_tiles(basemaps.NASAGIBS.ModisTerraTrueColorCR, days_slider.value)\n",
    "\n",
    "m.layers = [base_layer]\n",
    "m.zoom = 6"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def handle_day_change(change, **kwargs):\n",
    "    base_layer.url = basemaps.NASAGIBS.ModisTerraTrueColorCR.get('url') % change['new']\n",
    "\n",
    "days_slider.observe(handle_day_change, 'value')\n",
    "\n",
    "days_slider"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# jupyterlab-sidecar: A sidecar output widget for JupyterLab\n",
    "\n",
    "## https://github.com/jupyter-widgets/jupyterlab-sidecar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sidecar import Sidecar\n",
    "\n",
    "sc = Sidecar(title='Map Output')\n",
    "with sc:\n",
    "    m.layout.height = ''\n",
    "    display(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Marker"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mark = Marker(location=m.center)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m += mark"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mark.visible"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mark.visible = False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mark.visible = True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mark.interact(opacity=(0.0, 1.0, 0.01))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Popup\n",
    "\n",
    "The popup is displayed when you click on the marker. You can add ANY widget as a popup for the marker, from simple HTMLWidget to plots using bqplot."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import FloatSlider, link\n",
    "\n",
    "slider = FloatSlider(value=1.0, min=0.0, max=1.0)\n",
    "link((mark, 'opacity'), (slider, 'value'))\n",
    "\n",
    "mark.popup = slider"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Marker Cluster\n",
    "\n",
    "Markers can be clustered depending on the zoom level."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "xscale = 5\n",
    "yscale = 10\n",
    "\n",
    "x = [m.center[0] + i * xscale * .05 for i in (-1,0,1)]\n",
    "y = [m.center[1] + i * yscale * .05 for i in (-1,0,1)]\n",
    "\n",
    "from itertools import product\n",
    "locations = product(x, y)\n",
    "markers = [Marker(location=loc) for loc in locations]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `MarkerCluster` will automatically handle clustering and the zoom level changes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "marker_cluster = MarkerCluster(markers=markers)\n",
    "m += marker_cluster"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Heatmap"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import Button, IntSlider, link\n",
    "from ipyleaflet import Heatmap\n",
    "from random import gauss\n",
    "import time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "center = (37.09, -103.66)\n",
    "zoom = 5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_random_data(length):\n",
    "    \"Return a list of some random lat/lon/value triples.\"\n",
    "    return [[gauss(center[0], 2), \n",
    "             gauss(center[1], 4),\n",
    "             gauss(700, 300)] for i in range(length)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m.center = center\n",
    "m.zoom = zoom"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "heat = Heatmap(locations=create_random_data(1000), radius=20, blur=10)\n",
    "m.add_layer(heat)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def generate(_):\n",
    "    heat.locations = create_random_data(1000)\n",
    "\n",
    "button = Button(description='Generate data', button_style='success')\n",
    "button.on_click(generate)\n",
    "button"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "slider = IntSlider(min=10, max=30, value=heat.radius)\n",
    "link((slider, 'value'), (heat, 'radius'))\n",
    "slider"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Controls"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# The following NASA images need a zoom level <= 9\n",
    "if m.zoom > 9:\n",
    "    m.zoom = 9\n",
    "\n",
    "control = SplitMapControl(\n",
    "    left_layer=basemap_to_tiles(basemaps.NASAGIBS.ModisTerraTrueColorCR, \"2017-11-11\") , \n",
    "    right_layer=basemap_to_tiles(basemaps.NASAGIBS.ModisAquaBands721CR, \"2017-11-11\")\n",
    ")\n",
    "m.add_control(control)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m.remove_control(control)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import IntSlider, Button, link\n",
    "\n",
    "button = Button(description='Goto NYC')\n",
    "\n",
    "def goto_nyc(*args, **kwargs):\n",
    "    # NYC: 40.7128° N, 74.0060° W\n",
    "    m.center = (40.7128, -74.0060)\n",
    "    m.zoom = 9\n",
    "\n",
    "button.on_click(goto_nyc)\n",
    "\n",
    "wid_control = WidgetControl(widget=button, position='bottomright')\n",
    "m.add_control(wid_control)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## JupyterLab Themes Support\n",
    "\n",
    "Controls in ipyleaflet have support for JupyterLab themes, try changing the current theme"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m.remove_control(wid_control)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Advanced example 1: Velocity\n",
    "\n",
    "*It is ok to skip this for now -- running it requires downloading a ~1MB data file.*\n",
    "\n",
    "This example needs an extra dependency that is not in the installation instructions and a dataset.\n",
    "\n",
    "You can install it with this:\n",
    "\n",
    "```bash\n",
    "pip install netcdf4\n",
    "```\n",
    "\n",
    "To download the data, copy and paste the code below into a new cell and run it.\n",
    "\n",
    "```python\n",
    "import requests\n",
    "\n",
    "wind_data = requests.get('https://github.com/benbovy/xvelmap/raw/master/notebooks/wind-global.nc')  \n",
    "\n",
    "with open('wind-global.nc', 'wb') as f:  \n",
    "    f.write(wind_data.content)\n",
    "```\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipyleaflet.velocity import Velocity\n",
    "import xarray as xr"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "center = (0, 0)\n",
    "zoom = 4\n",
    "\n",
    "m2 = Map(center=center, zoom=zoom, interpolation='nearest', basemap=basemaps.CartoDB.DarkMatter)\n",
    "\n",
    "sc2 = Sidecar(title='Map Velocity')\n",
    "\n",
    "with sc2:\n",
    "    display(m2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ds = xr.open_dataset('wind-global.nc')\n",
    "display_options = {\n",
    "    'velocityType': 'Global Wind',\n",
    "    'displayPosition': 'bottomleft',\n",
    "    'displayEmptyString': 'No wind data'\n",
    "}\n",
    "wind = Velocity(data=ds,\n",
    "                zonal_speed='u_wind',\n",
    "                meridional_speed='v_wind',\n",
    "                latitude_dimension='lat',\n",
    "                longitude_dimension='lon',\n",
    "                velocity_scale=0.01,\n",
    "                max_velocity=20,\n",
    "                display_options=display_options)\n",
    "m2.add_layer(wind)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Advanced example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import Text, HTML, HBox\n",
    "from ipyleaflet import GeoJSON, WidgetControl, Map \n",
    "import json"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m = Map(center = (43,-100), zoom = 4)\n",
    "\n",
    "geo_json_data = json.load(open('us-states-density-colored.json'))\n",
    "geojson = GeoJSON(data=geo_json_data, hover_style={'color': 'black', 'dashArray': '5, 5', 'weight': 2})\n",
    "m.add_layer(geojson)\n",
    "\n",
    "html = HTML('''\n",
    "    <h4>US population density</h4>\n",
    "    Hover over a state\n",
    "''')\n",
    "html.layout.margin = '0px 20px 20px 20px'\n",
    "control = WidgetControl(widget=html, position='topright')\n",
    "m.add_control(control)\n",
    "\n",
    "def update_html(properties, **kwargs):\n",
    "    html.value = '''\n",
    "        <h4>US population density</h4>\n",
    "        <h2><b>{}</b></h2>\n",
    "        {} people / mi^2\n",
    "    '''.format(properties['name'], properties['density'])\n",
    "\n",
    "geojson.on_hover(update_html)\n",
    "\n",
    "m"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Exercise"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1) Create a Map"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2) Create a slider controlling the zoom level of the Map (manually, not using interact)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3) Embed the slider in a WidgetControl on the bottom left of the map"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "widgets-tutorial",
   "language": "python",
   "name": "widgets-tutorial"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
